﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using WindowsPhoneEssentials.Abstractions;
using WindowsPhoneEssentials.Testing;
using WindowsPhoneEssentials.Testing.Abstractions;
using WindowsPhoneEssentials.Testing.Threading;
using WindowsPhoneEssentials.Threading;
using WindowsPhoneMVC.ActionResults;
using WindowsPhoneMVC.Framework;
using WindowsPhoneMVC.Navigation;
using WindowsPhoneMVC.Navigation.Interfaces;
using WindowsPhoneMVC.Tests.ActionResults.Fakes;
using WindowsPhoneMVC.Tests.Navigation.Fakes;
using WindowsPhoneMVC.Tests.Phone.Fakes;

namespace WindowsPhoneMVC.Tests.Navigation
{
    [TestClass]
    public class NavigatorTests
    {
        private FakeControllerLocator fakeControllerLocator;
        private Navigator navigatorUnderTest;
        private FakeNavigationContextFactory fakeNavigationContextFactory;
        private FakeJournal fakeJournal;
        private IPhoneApplicationService phoneApplicationService;

        [TestInitialize]
        public void Setup()
        {
            fakeControllerLocator = new FakeControllerLocator();
            Execute.SetOnUIThreadPassthrough();
            fakeNavigationContextFactory = new FakeNavigationContextFactory();

            navigatorUnderTest = new Navigator(
                fakeControllerLocator, 
                fakeNavigationContextFactory,
                new TestBackgroundWorker(),
                new FakeControllerFactory(), 
                new DefaultActionResolver());

            var mvcFrame = new FakeMvcFrame();
            fakeJournal = new FakeJournal();
            phoneApplicationService = new FakePhoneApplicationService();
            fakeJournal.Initialise(phoneApplicationService);
            navigatorUnderTest.Initialise(fakeJournal, mvcFrame, "TestFrame");
        }

        [TestMethod]
        public void can_navigate_to_controller_action()
        {
            // arrange
            fakeControllerLocator.Register<FakeController>();
            var testViewResult = new FakeViewResult(navigatorUnderTest);
            //fakeActionInvokerFactory.Register(typeof(FakeController), "SomeAction", testViewResult);

            // act
            navigatorUnderTest.Invoke<FakeController>("SomeAction");

            // assert
            Assert.IsTrue(testViewResult.Executed);
        }

        [TestMethod]
        public void should_not_dispose_last_view_result_if_navigation_fails()
        {
            // arrange
            var testViewResult = new FakeViewResult(navigatorUnderTest);
            fakeControllerLocator.Register<FakeController>();
            //fakeActionInvokerFactory.Register(typeof(FakeController), "SomeAction", testViewResult);

            // act
            navigatorUnderTest.Invoke<FakeController>("SomeAction");
            try
            {
                navigatorUnderTest.Invoke<FakeController>("ThrowExceptionAction");
            }// ReSharper disable EmptyGeneralCatchClause
            catch // ReSharper restore EmptyGeneralCatchClause
            {}

            // assert
            Assert.IsFalse(testViewResult.Disposed);
        }

        [TestMethod]
        public void flushes_journal_on_save_state()
        {
            // arrange
            fakeControllerLocator.Register<FakeController>();
            //fakeActionInvokerFactory.Register(typeof(FakeController), "SomeAction", new FakeViewResult(navigatorUnderTest));
            navigatorUnderTest.Invoke<FakeController>("SomeAction");

            // act
            navigatorUnderTest.SaveState();

            // assert
            Assert.AreEqual(1, fakeJournal.FlushReceivedCount);
        }

        [TestMethod]
        public void does_not_dispose_view_when_non_viewresult_actionresult_is_executed()
        {
            // arrange
            fakeControllerLocator.Register<FakeController>();
            var testViewResult = new FakeViewResult(navigatorUnderTest);
            //fakeActionInvokerFactory.Register(typeof(FakeController), "SomeAction", testViewResult);
            //fakeActionInvokerFactory.Register(typeof(FakeController), "DoNothing", new DoNothingResult(null));
            navigatorUnderTest.Invoke("TestController", "SomeAction");

            // act
            navigatorUnderTest.Invoke("TestController", "DoNothing");

            // assert
            Assert.IsFalse(testViewResult.Disposed);
        }

        [TestMethod]
        public void does_dispose_view_when_viewresult_actionresult_is_executed()
        {
            // arrange
            fakeControllerLocator.Register<FakeController>();
            var testViewResult = new FakeViewResult(navigatorUnderTest);
            //fakeActionInvokerFactory.Register(typeof(FakeController), "SomeAction", testViewResult);
            navigatorUnderTest.Invoke("TestController", "SomeAction");

            // act
            navigatorUnderTest.Invoke("TestController", "SomeAction");

            // assert
            Assert.IsTrue(testViewResult.Disposed);
        }

        [TestMethod]
        public void calls_completed_event_on_actionresult_that_is_not_view()
        {
            // arrange
            fakeControllerLocator.Register<FakeController>();
            var testResultWithComplete = new TestResultWithComplete(navigatorUnderTest);
            //fakeActionInvokerFactory.Register(typeof(FakeController), "SomeAction", testResultWithComplete);

            // act
            navigatorUnderTest.Invoke("TestController", "SomeAction");

            // assert
            Assert.IsTrue(testResultWithComplete.CompleteCalled);
        }

        [TestMethod]
        public void navigations_with_results_can_return_from_controller_actions()
        {
            // arrange
            fakeControllerLocator.Register<FakeController>();
            var dataResult = new DataResult(fakeNavigationContextFactory.Create(navigatorUnderTest, new NavigationRequest()), "testresult");
            //fakeActionInvokerFactory.Register(typeof(FakeController), "ActionWithResult", dataResult);
            string result = null;

            // act
            navigatorUnderTest.InvokeWithResult<string>("TestController", "ActionWithResult", r => result = r);

            // assert
            Assert.AreEqual("testresult", result);
        }

        [TestMethod]
        public void navigations_with_result_can_return_result_from_another_view()
        {
            // arrange
            fakeControllerLocator.Register<FakeController>();
            //fakeActionInvokerFactory.Register(typeof(FakeController), "SomeAction", new FakeViewResult(navigatorUnderTest));
            string result = null;

            // act
            navigatorUnderTest.Invoke("TestController", "SomeAction");
            navigatorUnderTest.InvokeWithResult<string>("TestController", "SomeAction", r => result = r);
            navigatorUnderTest.NavigationResult("anothertestresult");

            // assert
            Assert.AreEqual("anothertestresult", result);
        }

        [TestMethod]
        public void cannot_navigate_while_waiting_on_result()
        {
            // arrange
            fakeControllerLocator.Register<FakeController>();
            //fakeActionInvokerFactory.Register(typeof(FakeController), "SomeAction", new FakeViewResult(navigatorUnderTest));

            // act
            navigatorUnderTest.InvokeWithResult<string>("TestController", "SomeAction", r => {});

            // assert
            AssertEx.Throws<InvalidOperationException>(()=>navigatorUnderTest.Invoke<FakeController>("SomeAction"));
        }

        [TestMethod]
        public void back_cancels_waiting_for_result()
        {
            // arrange
            fakeControllerLocator.Register<FakeController>();
            //fakeActionInvokerFactory.Register(typeof(FakeController), "SomeAction", new FakeViewResult(navigatorUnderTest));

            // act
            navigatorUnderTest.Invoke("TestController", "SomeAction");
            navigatorUnderTest.InvokeWithResult<string>("TestController", "SomeAction", r => { });
            navigatorUnderTest.NavigateBack();

            // assert 
            navigatorUnderTest.Invoke("TestController", "SomeAction"); //Will throw if back doesnt cancel (previous test verifies that)
        }

        public class TestResultWithComplete : ActionResult, IHaveNavigationCompletedSteps
        {
            public TestResultWithComplete(INavigator navigator) : base(new FakeNavigationContext(navigator, new NavigationRequest()))
            {
            }

            protected override void ExecuteInternal()
            {
                
            }

            public void NavigationComplete()
            {
                CompleteCalled = true;
            }

            public bool CompleteCalled { get; private set; }
        }

        public class TestIsDisposedViewModel : ViewModelBase, IDisposable
        {
            public void Dispose()
            {
                IsDisposed = true; 
            }

            public override void OnActivated()
            {
                base.OnActivated();
                ActivationCount++;
            }

            public int ActivationCount { get; set; }

            public bool IsDisposed { get; private set; }
        }

        public class Poco
        {
        }
    }
}
